﻿using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.Web.Mvc;
using TSharp.Core.Mvc.Properties;

namespace TSharp.Core.Mvc.Extensions
{
    /// <summary>
    ///     表达式转路由集合
    ///     <para>2010/8/19</para>
    ///     <para>TANGJINGBO</para>
    ///     <para>tangjingbo</para>
    /// </summary>
    public static class ExpressionValuesHelper
    {
        /// <summary>
        ///     Gets the route values from expression.
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        /// <param name="action">The action.</param>
        /// <param name="values">The values.</param>
        /// <returns></returns>
        public static RouteValueDictionary GetRouteValuesFromExpression<TController>(
            Expression<Action<TController>> action, RouteValueDictionary values)
            where TController : IController
        {
            RouteValueDictionary rvd = GetRouteValuesFromExpression(action);
            if (values == null)
                return (rvd);
            if (rvd != null)
                foreach (var kv in rvd)
                    values[kv.Key] = kv.Value; //values中的值将被覆盖
            return values;
        }

        /// <summary>
        ///     从lambda表达式获取路由
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public static RouteValueDictionary GetRouteValuesFromExpression<TController>(
            Expression<Action<TController>> action) where TController : IController
        {
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            var call = action.Body as MethodCallExpression;
            if (call == null)
            {
                throw new ArgumentException(MvcResources.ControllerExtensions_MustBeMethodCall, "action");
            }
            Type ctlType = typeof (TController);
            string controllerName = ctlType.Name;
            if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException(MvcResources.ControllerExtensions_TargetMustEndInController, "action");
            }
            controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);
            if (controllerName.Length == 0)
            {
                throw new ArgumentException(MvcResources.ControllerExtensions_CannotRouteToController, "action");
            }

            // TODO: How do we know that this method is even web callable?
            //      For now, we just let the call itself throw an exception.

            var rvd = new RouteValueDictionary();
            rvd.Add("Controller", controllerName);
            string actionName = call.Method.Name;
            if (actionName.EndsWith("Async", StringComparison.OrdinalIgnoreCase))
                actionName = actionName.Substring(0, actionName.Length - "Async".Length);
            rvd.Add("Action", actionName);
            AddParameterValuesFromExpressionToDictionary(rvd, call);
            return rvd;
        }

        //private static FastLambda.FastEvaluator fe = new FastLambda.FastEvaluator();
        /// <summary>
        ///     Adds the parameter values from expression to dictionary.
        /// </summary>
        /// <param name="rvd">The RVD.</param>
        /// <param name="call">The call.</param>
        private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd,
                                                                         MethodCallExpression call)
        {
            ParameterInfo[] parameters = call.Method.GetParameters();

            if (parameters.Length > 0)
            {
                for (int i = 0; i < parameters.Length; i++)
                {
                    Expression arg = call.Arguments[i];

#if true
                    // change method from FastEvaluator to microsoft.web.mvc
                    //object value = fe.Eval(arg);
                    object value = CachedExpressionCompiler.Evaluate(arg);
#else
                    object value;
                    var ce = arg as ConstantExpression;
                    if (ce != null)
                    {
                        // If argument is a constant expression, just get the value
                        value = ce.Value;
                    }
                    else
                    {
                        // Otherwise, convert the argument subexpression to type object,
                        // make a lambda out of it, compile it, and invoke it to get the value
                        Expression<Func<object>> lambdaExpression =
                            Expression.Lambda<Func<object>>(Expression.Convert(arg, typeof(object)));
                        Func<object> func = lambdaExpression.Compile();
                        value = func();
                    }
#endif
                    rvd.Add(parameters[i].Name, value);
                }
            }
        }

        [Obsolete]
        private static RedirectToRouteResult RedirectToActionInternal_Obsolete<TController>(
            Expression<Action<TController>> action, RouteValueDictionary values)
            where TController : IController
        {
            var body = action.Body as MethodCallExpression;
            if (body == null)
                throw new InvalidOperationException("Expression must be a method call.");
            if (body.Object != action.Parameters[0])
                throw new InvalidOperationException("Method call must target lambda argument.");
            string actionName = body.Method.Name;
            string controllerName = typeof (TController).Name;
            if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
                controllerName = controllerName.Remove(controllerName.Length - 10, 10);
            RouteValueDictionary parameters = LinkBuilder.BuildParameterValuesFromExpression(body);
            values = values ?? new RouteValueDictionary();
            values.Add("controller", controllerName);
            values.Add("action", actionName);
            if (parameters != null)
            {
                foreach (var parameter in parameters)
                {
                    values.Add(parameter.Key, parameter.Value);
                }
            }
            return new RedirectToRouteResult(values);
        }
    }
}